package gamecore.clocks;

/**
 * @author Alberola Gustavo Alejandro
 *
 * Clock para controlar y medir la ejecución de los juegos
 */
abstract public class GameClock {

	//==============================================================================================
	//	ATRIBUTOS
	//==============================================================================================
	/**Velocidad de ejecución predefinida*/
	private int fps = 0;
	/**Velocidad mínima de fps permitida*/
	private int minFps = 0;
	/**Velocidad máxima de fps permitida*/
	private int maxFps = 0;
	/**Cantidad de intentos restantes hasta ejecutar el método yield()*/
	private int skipsToYield = 0;
	/**Cantidad de intentos hasta ejecutar el método yield()*/
	private int maxSkipsToYield = 0;
	/**Cantidad de frames que se pueden saltear en un ciclo*/
	private int maxFramesSkiped = 0;
	//----------------------------------------------------------------------------------------------
	//ATRIBUTOS PARA EL REGISTRO DE TIEMPOS
	/**Periodo de tiempo de cada ejecución. Obtenido a partir de los fps*/
	private long period = 0L;
	/**Tiempo en que comenzó la ejecución del GameCore*/
	private long timeStartCore = 0L;
	/**Tiempo en el que comienza la ejecución de un nuevo ciclo (update, render, refresh)*/
	private long timeStartCicle = 0L;
	/**Tiempo en el que finaliza la ejecución de un nuevo ciclo (update, render, refresh)*/
	private long timeEndCicle = 0L;
	/**Tiempo que tardó el ciclo en ser procesado*/
	private long timeCicleDifference = 0L;
	/**Tiempo que se establece para que el thread duerma*/
	private long timeSleep = 0L;
	/**Tiempo que el thread durmió de más*/
	private long timeSleepExcess = 0L;
	/**Tiempo que se acumula cada vez que el timeSleep es menor a 0*/
	private long timeExcessToSkip = 0L;

	//==============================================================================================
	//	CONSTRUCTORES
	//==============================================================================================
	public GameClock(){
		minFps = 10;
		maxFps = 60;
		fps = 50;
		calculatePeriod();
		maxSkipsToYield = 5; //Cada 5 ciclos que superen el período, se llama a yield()
		maxFramesSkiped = 5; //Cada ciclo se pueden llegar a saltear 5 frames
	}

	//==============================================================================================
	//	SETTERS
	//==============================================================================================
	public boolean setFps(int fps){
		//Siempre que los FPS estén dentro de los márgenes
		if ( fps >= minFps && fps <= maxFps ){
			this.fps = fps;		//Establecemos la nueva velocidad
			calculatePeriod();	//Recalculamos el período
			return true;
		}else{
			return false;
		}
	}

	//==============================================================================================
	//	GETTERS
	//==============================================================================================
	/**Convierte el tiempo de sueño en ns a ms y lo devuelve para que el thread duerma*/
	public int getSleepInMilliseconds(){ return (int)(timeSleep / 1000000L); }

	//==============================================================================================
	//	MÉTODOS DE REGISTRACIÓN DE LOS TIEMPOS
	//==============================================================================================
	/**A partir de los FPS se calcula el período. Primero se obtiene con 1000 / fps al tiempo en ms. Luego
		se realiza la conversión a ns.*/
	private void calculatePeriod(){ period = (long)( 1000 / fps ) * 1000000L; }
	/**Registra el momento en que el core comenzó con su ejecución*/
	public void registerStartCore() { timeStartCore = getTime(); }
	/**Registra el momento en que comienza un ciclo*/
	public void registerStartCicle() { timeStartCicle = getTime(); }
	/**Registra el momento en que termina un ciclo*/
	public void registerEndCicle() {
		timeEndCicle = getTime();
		timeCicleDifference = timeEndCicle - timeStartCicle; //Calculamos el tiempo que demoró el ciclo
	}
	/**Requiere el cálculo de tiempo que debe dormirse el thread*/
	public boolean requireSleep(){
		timeSleep = ( period - timeCicleDifference ) - timeSleepExcess;
		return ( timeSleep > 0 ) ? true : false;
	}
	/**Registra el tiempo de más que el thread estuvo durmiendo. Se llama cuando el tiempo del ciclo no superó el período*/
	public void registerSleepTime(){
		timeSleepExcess = getTime() - ( timeEndCicle + timeSleep );
	}
	/**Obtiene si es necesario llamar al método yield. Se llama cuando el tiempo del ciclo superó el período*/
	public boolean requiereYield(){
		timeSleepExcess = 0L; //El exceso de tiempo se establece a 0 dado que no durmió el thread
		if ( skipsToYield++ > maxSkipsToYield ){
			skipsToYield = 0; //Reiniciamos el contador
			return true;
		}else
			return false;
	}

	public int requiereFramesToSkip(){
		int framesSkipped = 0;
		//En caso de que el tiempo de sueño haya sido menor a 0 (más tiempo que el período), el excedente
		//se almacena en timeSleepExcess.
		timeExcessToSkip += (timeSleep < 0 ) ? -timeSleep : 0;
		while ( timeExcessToSkip > period && framesSkipped < maxFramesSkiped ){
			timeExcessToSkip -= period;	//Le restamos un período. Puede haberse excedido más de 1 período
			framesSkipped++;			//Sumamos 1 a la cantidad de frames a saltear
		}
		return framesSkipped; //El core debería encargarse de realizar las x llamadas a gameUpdate()
	}
	//==============================================================================================
	//	MÉTODO ABSTRACTO PARA REDEFINIR EN LA IMPLEMENTACIÓN
	//==============================================================================================
	/**Devuelve el tiempo actual en ns*/
	abstract public long getTime();

	@Override
	public String toString(){
		return "period:"+period+"#timeStartCicle:"+timeStartCicle+"#timeEndCicle:"+timeEndCicle+"#timeCicleDifference:"+timeCicleDifference+"#timeSleep:"+timeSleep+"#timeSleepExcess:"+timeSleepExcess+"#timeExcessToSkip:"+timeExcessToSkip;
	}
}
